﻿// Inner Fire 游戏引擎库
// DxInstancedSprite 渲染器实例精灵绘制
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 用于DirectX的渲染器。
// 此文件分装用于实例精灵绘制，调试线段绘制等。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-05-21

#include "dx_instanced_sprite.h"
#include "dx_graphics.h"
#include <dx/mesh/dx_mesh.h>

namespace ifire::dx {
DxInstancedSprite::DxInstancedSprite(DxGraphics* dx) : DxModule(dx) {}

void DxInstancedSprite::Update() {
  auto curr_frame = dx_->CurrentFrameRes();

  // 更新实例精灵
  auto main_buffer = curr_frame->instanced_sprite_cb.get();
  for (auto& e : instanced_sprites_) {
    auto sprite_buffer = curr_frame->instanced_sprite.Get(e->index);
    e->Update(main_buffer, sprite_buffer);
  }

  // 更新地图
  auto map_buffer = curr_frame->map_block_cb.get();
  for (auto& e : map_blocks_) {
    e->Update(map_buffer);
  }
}

InstancedSprites* DxInstancedSprite::Create(
    InstancedSpriteType type, DxMesh* mesh, size_t count) {
  // 创建一个BUFFER，得到Index
  int index = dx_->GetFrameRes().CreateInstancedBuffer(count);
  // 创建一个Pool，Index为刚才得到的
  auto item = std::make_unique<InstancedSprites>(type, mesh, count);
  item->index = index;

  // 根据类型设置绘制方式
  if (type == InstancedSpriteType::Line) {
    item->primitive_type = D3D_PRIMITIVE_TOPOLOGY_LINELIST;
  }

  // 返回指针
  return instanced_sprites_.emplace_back(std::move(item)).get();
}

MapBlocks* DxInstancedSprite::CreateMapBlocks(
    DxMesh* mesh, ikit::sstr sub_mesh, size_t count) {

  // 创建对象
  auto item = std::make_unique<MapBlocks>(mesh, sub_mesh, count);
  item->index = static_cast<int>(map_blocks_.size());

  // 创建项目
  item->SetData(std::make_unique<UploadBuffer<MapBlockData>>(
      dx_->Device(), count, false));

  // 返回指针
  return map_blocks_.emplace_back(std::move(item)).get();
}

void DxInstancedSprite::Draw(ID3D12GraphicsCommandList* cmd) {

  // TIPS:我在这里犯了一个错误，居然开始的时候没有更换根签名。
  // 但是因为我的结构和Standard根签名一样，所以居然没有出错。
  // 我只有最后一个结构化缓冲区不一样，Standard是一个常量缓冲区。
  // 但不知道为什么。他居然能够用！后来我看到Direct的警告，才知道我没换根签名。
  // 我觉得这个粒子系统可以使用和Standard一样的根签名。

  // 将根签名，Pass设置为粒子系统。
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mParticle.Get());
  // auto particle_pass_cb = dx_->CurrentFrameRes()->particle_pass->Resource();
  // cmd->SetGraphicsRootConstantBufferView(
  //     ShaderSlot::MAIN_PASS, particle_pass_cb->GetGPUVirtualAddress());

  // 得到常量CB
  auto cb = dx_->CurrentFrameRes()->instanced_sprite_cb.get();

  // 分两次绘制，一次是普通的，一次是线段
  DrawTypeGroups(cmd, cb, InstancedSpriteType::Point, "Particle");
  DrawTypeGroups(cmd, cb, InstancedSpriteType::Line, "ParticleLine");
  DrawTypeGroups(cmd, cb, InstancedSpriteType::Mesh, "ParticleMesh");

  // 绘制地图
  auto map_cb = dx_->CurrentFrameRes()->map_block_cb.get();
  DrawMapBlocks(cmd, map_cb, "MapBlock");

  // 将根签名和Pass设置回去
  cmd->SetGraphicsRootSignature(dx_->GetRootsign().mStandard.Get());
  auto pass_cb = dx_->CurrentFrameRes()->pass_cb->Resource();
  cmd->SetGraphicsRootConstantBufferView(
      ShaderSlot::MAIN_PASS, pass_cb->GetGPUVirtualAddress());
}

void DxInstancedSprite::DrawTypeGroups(ID3D12GraphicsCommandList* cmd,
    UploadBuffer<InstancedSpriteConstants>* cb, InstancedSpriteType type,
    const char* pipeline) {
  auto pipe = dx_->GetDraw().GetPipeline(pipeline);
  if (pipe == nullptr) return;

  cmd->SetPipelineState(dx_->GetPipeline()[pipeline]);
  for (auto& item : instanced_sprites_) {
    if (item->GetDrawCount() > 0 && item->GetSpriteType() == type) {
      DrawItem(cmd, cb, item.get());
    }
  }
}

void DxInstancedSprite::DrawItem(ID3D12GraphicsCommandList* cmd,
    UploadBuffer<InstancedSpriteConstants>* cb, InstancedSprites* item) {
  // 用于Constant的大小
  constexpr auto CB_SIZE =
      calc_constant_buffer_size(sizeof(InstancedSpriteConstants));

  // 绑定顶点与索引
  auto vertex_view = item->GetMesh()->VertexBufferView();
  cmd->IASetVertexBuffers(0, 1, &vertex_view);
  auto index_view = item->GetMesh()->IndexBufferView();
  cmd->IASetIndexBuffer(&index_view);
  cmd->IASetPrimitiveTopology(item->primitive_type);

  // 新式使用根常量缓冲区
  D3D12_GPU_VIRTUAL_ADDRESS obj_cb_address =
      cb->Resource()->GetGPUVirtualAddress() + item->index * CB_SIZE;
  cmd->SetGraphicsRootConstantBufferView(ShaderSlot::OBJECT_CB, obj_cb_address);

  // 设置实例化的对象列表
  auto data_cb = dx_->CurrentFrameRes()->instanced_sprite.Get(item->index);
  cmd->SetGraphicsRootShaderResourceView(
      ShaderSlot::PARTICLE_DATA, data_cb->Resource()->GetGPUVirtualAddress());

  // 如果是Mesh，要设置贴图
  if (item->GetSpriteType() == InstancedSpriteType::Mesh) {
    auto material_index = item->GetMaterial()->diffuse_index;
    cmd->SetGraphicsRootDescriptorTable(ShaderSlot::TEXTURE,
        dx_->GetDescriptor().GetTextureGpu(material_index));
  }

  // 进行绘制
  auto index_count = item->GetMesh()->DefaultSubmesh().index_count;
  cmd->DrawIndexedInstanced(index_count, item->GetDrawCount(), 0, 0, 0);
}

void DxInstancedSprite::DrawMapBlocks(ID3D12GraphicsCommandList* cmd,
    UploadBuffer<InstancedSpriteConstants>* cb, const char* pipeline) {
  auto pipe = dx_->GetDraw().GetPipeline(pipeline);
  if (pipe == nullptr) return;

  cmd->SetPipelineState(dx_->GetPipeline()[pipeline]);
  for (auto& item : map_blocks_) {
    DrawMapBlock(cmd, cb, item.get());
  }
}

void DxInstancedSprite::DrawMapBlock(ID3D12GraphicsCommandList* cmd,
    UploadBuffer<InstancedSpriteConstants>* cb, MapBlocks* item) {
  // 用于Constant的大小
  constexpr auto CB_SIZE =
      calc_constant_buffer_size(sizeof(InstancedSpriteConstants));

  // 绑定顶点与索引
  auto vertex_view = item->GetMesh()->VertexBufferView();
  cmd->IASetVertexBuffers(0, 1, &vertex_view);
  auto index_view = item->GetMesh()->IndexBufferView();
  cmd->IASetIndexBuffer(&index_view);
  cmd->IASetPrimitiveTopology(item->primitive_type);

  // 新式使用根常量缓冲区
  D3D12_GPU_VIRTUAL_ADDRESS obj_cb_address =
      cb->Resource()->GetGPUVirtualAddress() + item->index * CB_SIZE;
  cmd->SetGraphicsRootConstantBufferView(ShaderSlot::OBJECT_CB, obj_cb_address);

  // 设置实例化的对象列表
  auto& data_cb = item->data();
  cmd->SetGraphicsRootShaderResourceView(
      ShaderSlot::PARTICLE_DATA, data_cb->Resource()->GetGPUVirtualAddress());

  // 设置贴图
  auto material_index = item->GetMaterial()->diffuse_index;
  cmd->SetGraphicsRootDescriptorTable(
      ShaderSlot::TEXTURE, dx_->GetDescriptor().GetTextureGpu(material_index));

  // 进行绘制
  auto index_count = item->GetMesh()->DefaultSubmesh().index_count;
  cmd->DrawIndexedInstanced(index_count, item->GetDrawCount(), 0, 0, 0);
}

} // namespace ifire::dx